package com.mxx.common.netty.ws;

import com.mxx.common.netty.http.NettyHttpHandler;
import com.mxx.common.netty.http.NettyHttpRequest;
import com.mxx.common.netty.http.NettyHttpResponseFactory;
import com.mxx.common.conv.JacksonConverter;
import com.mxx.common.netty.NettyWebsocketOperator;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;

import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

/**
 * @Auther: Mr. Zeng
 * @Date: 2020/7/17 15:21
 * @Description: 暂无描述
 */
@Slf4j
@ChannelHandler.Sharable
public class NettyWebsocketConnectionForwardHandler extends ChannelInboundHandlerAdapter implements ApplicationContextAware {
    private WebSocketServerHandshaker handshaker;

    private NettyWebsocketOperator websocketOperator;

    private JacksonConverter jacksonConverter;
    private NettyHttpResponseFactory responseFactory;

    @Override

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest) {

            FullHttpRequest httpRequest = (FullHttpRequest) msg;

            if (!httpRequest.decoderResult().isSuccess()) {

                DefaultFullHttpResponse httpResponse = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);

                // 返回应答给客户端
                if (httpResponse.status().code() != 200) {
                    ByteBuf buf = Unpooled.copiedBuffer(httpResponse.status().toString(), CharsetUtil.UTF_8);
                    httpResponse.content().writeBytes(buf);
                    buf.release();
                }

                // 如果是非Keep-Alive，关闭连接
                ChannelFuture f = ctx.channel().writeAndFlush(httpResponse);
                if (httpResponse.status().code() != 200) {
                    f.addListener(ChannelFutureListener.CLOSE);
                }
                return;
            }
            /**
             * 鉴定是否为WebSocket 请求
             */
            CharSequence version = httpRequest.headers().get(HttpHeaderNames.SEC_WEBSOCKET_VERSION);
            if (version == null) {
                ctx.pipeline().remove(NettyWebsocketHandler.class);
                ctx.fireChannelRead(msg);
                return;
            } else {
                /**
                 * WebSocket 注册请求鉴权
                 */

                NettyHttpRequest request = new NettyHttpRequest(ctx, httpRequest, jacksonConverter);

                if (!websocketOperator.open(ctx, request)) {
                    ReferenceCountUtil.release(request);
                    ctx.writeAndFlush(responseFactory.newResponse(HttpResponseStatus.UNAUTHORIZED)).addListener(ChannelFutureListener.CLOSE);
                    return;
                }

                /**
                 * WebSocket 注册请求处理
                 */
                WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws:/" + ctx.channel() + "/websocket", null, false);
                handshaker = wsFactory.newHandshaker(httpRequest);

                if (null == handshaker) {
                    WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
                } else {
                    handshaker.handshake(ctx.channel(), httpRequest);
                }
                return;
            }
        }
        /**
         * WebSocket 请第二次请求时 :移除 Http请求处理器 -> 执行完毕后 -> 再 移除转发处理器, 提高请求处理效率
         */

        ctx.pipeline().remove(NettyHttpHandler.class);
        ctx.fireChannelRead(msg);
        ctx.pipeline().remove(NettyWebsocketConnectionForwardHandler.class);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        jacksonConverter = applicationContext.getBean(JacksonConverter.class);
        responseFactory = applicationContext.getBean(NettyHttpResponseFactory.class);
        websocketOperator = applicationContext.getBean(NettyWebsocketOperator.class);
    }
}
